#include "CURLTransport.h"
#include "MsnConnection.h"
#include "Util.h"
#include "MsnDef.h"
#include "MsnCommand.h"


CURLTransport::CURLTransport()
{
	curl = NULL;
	gatewayIP = MSN_HTTP_SERVER_ADDRESS;
}

CURLTransport::~CURLTransport()
{

	for(DataList::iterator it = dataList.begin();it != dataList.end(); ++it){
        DataHolder* holder = *it;			
		delete holder;
	}	

	dataList.clear();
}


wxString CURLTransport::GetSessionID(const wxString& headers)
{
    wxString sessionID = StringUtil::SubString(headers, _T("SessionID="), _T(";"));;
    return sessionID;
}

wxString CURLTransport::GetGatewayIP(const wxString& headers)
{
	int start = headers.find(_T("GW-IP="));
    int end = headers.find(_T(";"),start);

	if(end == -1){
		end = headers.find(_T("\r\n"),start);
	}

    wxString sessionID = headers.SubString(start+6, end-1).Trim(false).Trim(true);
    return sessionID;
}

int CURLTransport::GetContentLength(const wxString& headers)
{

    wxString length =StringUtil::SubString(headers, _T("Content-Length:"), _T("\r\n"));
	return StringUtil::ToLong(length);	
}

void* CURLTransport::Entry()
{
	DEBUG(_T("CURLTransport thread start: ") + wxString::Format(_T("%d"),this->GetId()));

	curl = curl_easy_init();

	connected = this->Open();

	int sleepCounter = 0;
	int pollFailCounter = 0;
	while (connected && !TestDestroy()){
		
		if(closed){
			DEBUG(_T("Transport closed"));
			break;
		}

		if(!dataList.empty()){
			SendWaitingData();
		}

		wxThread::Sleep(200);
		sleepCounter ++;

		if(sleepCounter >= 10){
			sleepCounter = 0;

			bool pollResult = PollHttp();

			if(!pollResult){
				
				pollFailCounter++;

				DEBUG(_T("PollHttp failed........") + wxString::Format(_T("%d"), pollFailCounter));
				DEBUG(_T("PollHttp failed........"));	

				//if fail 3 times consecutively
				if(pollFailCounter >= 3){
					connection->ConnectionBroken();
				}
			}else{
				pollFailCounter = 0;
			}
		}
    }

	if(connected){
		this->ExitHttp();
	}

	curl_easy_cleanup(curl);

    DEBUG(_T("connection thread exit:")+ wxString::Format(_T("%d"),this->GetId()));
    return 0;
}

bool CURLTransport::Open()
{
	DEBUG(_T("open http transport to: ")+ serverAddress + _T(":")+wxString::Format(_T("%d"), serverPort));

	//try connecting 3 times
	bool result = false;
	int connectFailCounter = 0;

	while(!closed && connectFailCounter < 3){
		connectFailCounter++;
		result = this->OpenHttp();
		if(!result){
			curl_easy_cleanup(curl);
			curl = curl_easy_init();
		}else{
			break;
		}
	}

	//callback on connection
	if(result){
		connection->ConnectionOpened();	
	}else{
		connection->ConnectionFailed();
	}

	return result;
}

void CURLTransport::Close()
{

	connected = false;
	closed = true;
}

bool CURLTransport::OpenHttp()
{
	wxString url = _T("http://") + serverAddress + _T("/gateway/gateway.dll?");

	url << _T("Action=open");
	url << _T("&Server=") << serverType;
	url << _T("&IP=")<<serverAddress;

	wxMemoryBuffer headerBuffer;
	wxMemoryBuffer bodyBuffer;
	bool httpResult = PostHttp(url, 0, 0, headerBuffer, bodyBuffer);

	if(httpResult){
		//check HTTP response
		wxString respHeader = wxString((char*)headerBuffer.GetData(), wxConvUTF8, headerBuffer.GetDataLen());	

		if(!respHeader.Contains(_T("200 OK"))){
			wxString respBody = wxString((char*)bodyBuffer.GetData(), wxConvUTF8, bodyBuffer.GetDataLen());	
			DEBUG(_T("HTTP response error: \r\n") + respHeader + respBody);
			
			return false;
		}

		ProcessHttpHeader(headerBuffer);
		//ProcessHttpBody(bodyBuffer);
	}

	return httpResult;
}

bool CURLTransport::ExitHttp()
{
	DEBUG(_T("exit http session "));

	wxString url = _T("http://") + serverAddress + _T("/gateway/gateway.dll?");
	url += _T("SessionID=") + sessionID;

	wxString cmd = _T("OUT\r\n");
	wxCharBuffer cmdBuf = cmd.ToAscii();

	wxMemoryBuffer headerBuffer;
	wxMemoryBuffer bodyBuffer;

	return PostHttp(url, (unsigned char*)cmdBuf.data(), cmd.Length(), headerBuffer, bodyBuffer);
}


bool CURLTransport::PollHttp()
{
	wxString url = _T("http://") + serverAddress + _T("/gateway/gateway.dll?");
	url << _T("Action=poll&SessionID=") << sessionID;

	wxMemoryBuffer headerBuffer;
	wxMemoryBuffer bodyBuffer;

	bool httpResult = PostHttp(url, 0, 0, headerBuffer, bodyBuffer);

	if(httpResult){
		ProcessHttpHeader(headerBuffer);

		ProcessHttpBody(bodyBuffer);
	}

	return httpResult;
}


void CURLTransport::SendData(void* data, unsigned int size)
{
	wxMutexLocker lock(dataMutex);
	dataList.push_back(new DataHolder((char*)data, size));
}

void CURLTransport::SendWaitingData()
{
	DEBUG(_T("SendWaitingData"));

	wxMutexLocker lock(dataMutex);

	//send one message instead of sending a bunch
	if(!dataList.empty()){
		DataHolder* holder = dataList.front();
		dataList.pop_front();
		SendDataImpl(holder->data, holder->length);
		delete holder;
	}
	
/*
	for(DataList::iterator it = dataList.begin();it != dataList.end(); ++it){
        DataHolder* holder = *it;			
		SendDataImpl(holder->data, holder->length);
		delete holder;
	}	

	dataList.clear();
*/
}

void CURLTransport::SendDataImpl(void* data, unsigned int size)
{
	wxString url = _T("http://") + serverAddress + _T("/gateway/gateway.dll?");

	url += _T("SessionID=") + sessionID;

	wxMemoryBuffer headerBuffer;
	wxMemoryBuffer bodyBuffer;

	PostHttp(url, (unsigned char*)data, size, headerBuffer, bodyBuffer);	

	ProcessHttpHeader(headerBuffer);

	ProcessHttpBody(bodyBuffer);	
}

void CURLTransport::ProcessHttpHeader(wxMemoryBuffer& buf)
{
	wxString respHeader = wxString((char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());	

	//DEBUG( _T("<<<<<") + respHeader);

	serverAddress = GetGatewayIP(respHeader);
	sessionID = GetSessionID(respHeader);
}

void CURLTransport::ProcessHttpBody(wxMemoryBuffer& buf)
{
	wxString respBody = wxString((char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());
	if(!respBody.IsEmpty()){
		DEBUG( _T("<<<<<") + respBody);
	}


	int len = buf.GetDataLen();
	wxMemoryBuffer lineBuf;

    for(int i=0; i<len; i++){
        char c = buf[i];
		//skip 13
		if(c == 13){
            continue;
		}
		if(c == 10){
			wxString cmdLine((char*)lineBuf.GetData(), wxConvUTF8, lineBuf.GetDataLen());
			MsnCommand* cmd = MsnCommandFactory::Get().CreateCommand(cmdLine);

			if(cmd == NULL){
				DEBUG(_T("undefined command: ") + cmdLine);
				continue;
			}
	
			MsnCommandWrapper wrapper(cmd);	

			if(wrapper.IsPayloadCommand()){
				int len = wrapper.GetPayloadLength();
				wrapper.GetPayloadBuffer().AppendData(buf + i + 1, len);
				i += len;
			}

			connection->ProcessCommand(&wrapper);

			lineBuf = wxMemoryBuffer();
            continue;
		}

		lineBuf.AppendByte(c);
    }   
}


size_t CURLTransport::HttpWrite_Callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
    wxMemoryBuffer* buffer = (wxMemoryBuffer*) stream;

	buffer->AppendData(ptr, nmemb);

    return size*nmemb;
}


bool CURLTransport::PostHttp(const wxString& url, unsigned char* reqBuf, unsigned int reqSize, wxMemoryBuffer& headerBuf, wxMemoryBuffer& bodyBuf)
{
	wxCharBuffer urlChar = url.ToAscii();

	if(reqSize > 0){
		//DEBUG(_T(">>>>") + url);
		//wxString req(reqBuf, wxConvUTF8, reqSize);
		//DEBUG(_T(">>>>") + req);
	}

    if(curl) {
	
		curl_easy_setopt(curl, CURLOPT_URL, (unsigned char*)urlChar.data());

		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqBuf);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, reqSize);

		curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
	
		//set http proxy
		if(connection->GetConnectionType() == MSN_CONNECTION_HTTP_PROXY){
			wxString proxyAddress = connection->GetProxyAddress();

			curl_easy_setopt(curl, CURLOPT_PROXY, proxyAddress.mb_str());	
			curl_easy_setopt(curl, CURLOPT_PROXYPORT, connection->GetProxyPort());

			wxString userPwd = connection->GetProxyUsername() + _T(":") + connection->GetProxyPassword();

			curl_easy_setopt(curl, CURLOPT_USERPWD, userPwd.mb_str());	
		}

		//http proxy server
		//curl_easy_setopt(curl, CURLOPT_PROXY, "189.16.157.58");	
		//curl_easy_setopt(curl, CURLOPT_PROXYPORT, 80);
		//curl_easy_setopt(curl, CURLOPT_USERPWD, "user:password");

		//curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, WSLEN(cmd));

		//curl_easy_setopt(curl, CURLOPT_USERAGENT, _T("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"));

		curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &headerBuf);
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HttpWrite_Callback);

		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &bodyBuf);
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpWrite_Callback);
       
        CURLcode code = curl_easy_perform(curl);

		if(code == CURLE_OK){
			return true;
		}
        DEBUG(wxString::Format(_T("http post error: %d"), code));
    }

	return false;
}

